﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IEx.Common
{
    public class IOStreamReliable
    {
        /// <summary>
        /// The interval time (in miliseconds) between each try
        /// </summary>
        private int timeIntervalBetweenTries;
        private int numberOfRetries;

        /// <summary>
        /// Create an instance of <see cref="IOStreamReliable"/> with default number of retries is 5 and time interval between tries is 1000 miliseconds
        /// </summary>
        public IOStreamReliable()
            : this(5, 1000)
        {

        }

        /// <summary>
        /// Create an instance of <see cref="IOStreamReliable"/>
        /// </summary>
        /// <param name="numberOfRetries">Number of retries</param>
        /// <param name="timeIntervalBetweenTries">time interval (in miliseconds) between retries</param>
        public IOStreamReliable(int numberOfRetries, int timeIntervalBetweenTries)
        {
            this.numberOfRetries = numberOfRetries;
            if (timeIntervalBetweenTries >= 0)
                this.timeIntervalBetweenTries = timeIntervalBetweenTries;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="execution"></param>
        /// <returns></returns>
        public T Execute<T>(Func<T> execution) where T : class
        {
            if (execution == null)
                throw new ArgumentNullException("execution");
            int currentTries = 0;
            while (true)
            {
                try
                {
                    return execution();
                }
                catch (System.IO.IOException e)
                {
                    // Other exception??
                    if (!FileIsLocked(e))
                        throw e;
                    if (++currentTries > this.numberOfRetries)
                    {
                        throw new Exception("File is locked too long: " + e.ToString());
                    }

                    // Retry after a time
                    System.Threading.Thread.Sleep(this.timeIntervalBetweenTries);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="execution"></param>
        public void Execute(Action execution)
        {
            if (execution == null)
                throw new ArgumentNullException("execution");
            int currentTries = 0;
            while (true)
            {
                try
                {
                    execution();
                    break;
                }
                catch (System.IO.IOException e)
                {
                    // Other exception??
                    if (!FileIsLocked(e))
                        throw e;
                    if (++currentTries > this.numberOfRetries)
                    {
                        throw new Exception("File is locked too long: " + e.ToString());
                    }

                    // Retry after a time
                    System.Threading.Thread.Sleep(this.timeIntervalBetweenTries);
                }
            }
        }

        /// <summary>
        /// Check whether current file is locked by another process.
        /// Check for more detail about System error codes: http://msdn.microsoft.com/en-us/library/ms681381%28VS.85%29.aspx
        /// </summary>
        /// <param name="e"></param>
        /// <returns></returns>
        private bool FileIsLocked(System.IO.IOException e)
        {
            int errorCode = System.Runtime.InteropServices.Marshal.GetHRForException(e) & ((1 << 16) - 1);
            return errorCode == 32 || errorCode == 33;
        }
    }
}
